package xyz.lwm.lazycat.utility.scan;

import xyz.lwm.lazycat.utility.Assert;
import xyz.lwm.lazycat.utility.reflect.ClassUtil;
import xyz.lwm.lazycat.utility.resource.Resource;

import java.util.List;
import java.util.Objects;
import java.util.function.Predicate;
import java.util.stream.Collectors;

/**
 * pattern scanner for class
 *
 * @author lwm
 */
public class PatternClassScanner implements Scanner<String, Class<?>> {

    private final PatternResourceScanner scanner = new PatternResourceScanner();

    /**
     * 扫描
     * <pre>
     * pattern: 包名或类名, 支持通配符
     * 例如: 包名: com.massage.**  或 com.massage.util.*
     *      类名: com.massage.util.*.class
     * </pre>
     *
     * @param pattern 包名或类名
     * @param filter 结果过滤器
     * @return scanned classes
     */
    @Override
    public List<Class<?>> scan(String pattern, Predicate<Class<?>> filter) {
        checkSource(pattern);
        pattern = pkgPathToSlashPath(pattern);
        List<Resource> resources = scanner.scan(pattern, r -> r.getPath().endsWith(CLASS_SUFFIX));
        return resources.stream()
                .map(Resource::getClasspath)
                .filter(Objects::nonNull)
                .distinct()
                .map(this::classPathToClassname)
                .map(this::loadClassByClassname)
                .filter(Objects::nonNull)
                .filter(filter)
                .collect(Collectors.toList());
    }

    private void checkSource(String source) {
        Assert.notEmpty(source, "source");
        Assert.isTrue(!source.contains("/"), "source must be package path without separator `/` ");
        Assert.isTrue(!source.contains("\\"), "source must be package path without separator `\\` ");
        Assert.isTrue(!source.startsWith(FILE_PREFIX) && !source.startsWith(CLASSPATH_PREFIX),
                "source must be package path without starting with `file:` or `classpath:`");
    }

    // 调整路径, 主要是为了适配 matcher 的标准, 从而达到正则匹配
    private String pkgPathToSlashPath(String path) {
        boolean hasClassSuffix = path.endsWith(CLASS_SUFFIX);
        // remove .class
        if (hasClassSuffix) {
            path = path.substring(0, path.length() - CLASS_SUFFIX.length());
        }
        // replace dot to slash
        path = path.replace(DOT, SLASH);
        // 添加 class 后缀, 两者是有区别的, 前者是恢复输入的后缀;
        // 后者是因为直接扫描包的, 需要加通配符, 实际上就是在包名后加 /*.class
        path += hasClassSuffix ? CLASS_SUFFIX : String.valueOf(SLASH) + WILDCARD + CLASS_SUFFIX;
        // add classpath prefix
        return CLASSPATH_PREFIX + path;
    }

    /**
     * class 文件路径 转 全类名
     *
     * @param classpath classpath 路径
     * @return 全类名
     */
    private String classPathToClassname(String classpath) {
        classpath = classpath.replaceAll("\\\\+", String.valueOf(SLASH));
        return classpath.replace(SLASH, DOT).substring(0, classpath.length() - CLASS_SUFFIX.length());
    }

    private Class<?> loadClassByClassname(String classname) {
        try {
            return ClassUtil.forName(classname, null);
        } catch (Throwable e) {
            return null;
        }
    }

}
